package cn.ulyer.common.transcode.secret;

import cn.ulyer.common.annotation.Decrypt;
import cn.ulyer.common.annotation.DecryptProperty;
import cn.ulyer.common.exception.DecryptException;
import cn.ulyer.common.utils.ApplicationUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 解密通类
 *
 * @author：ulyer
 */
@Component
public class DecryptCompositeBean {

    @Autowired
    private List<TranscodeHandler> handlers;

    final Map<String, TranscodeHandler> handlerCaches = new ConcurrentHashMap<>();

    public Object decrypt(MethodParameter methodParameter, Object parameterObject, Decrypt annotation, NativeWebRequest nativeWebRequest) throws DecryptException {
        return deepDecrypt(methodParameter.getParameterName(), parameterObject, parameterObject, annotation, nativeWebRequest);
    }


    /**
     * @param currentParameterName
     * @param currentValue
     * @param sourceParameter
     * @param annotation
     * @param nativeWebRequest
     * @return
     */
    private Object deepDecrypt(Object currentParameterName, Object currentValue, Object sourceParameter, Decrypt annotation, NativeWebRequest nativeWebRequest) {
        if(currentValue == null){
            return null;
        }
        DecryptProperty[] decryptProperties = annotation.decryptProperties();
        Class<?> valueClazz = currentValue.getClass();
        if (String.class.isAssignableFrom(valueClazz)) {
            for (DecryptProperty decryptProperty : decryptProperties) {
                if (currentParameterName.equals(decryptProperty.name())) {
                    TranscodeHandler transcodeHandler = getHandler(StringUtils.isBlank(decryptProperty.algorithm())?annotation.defaultAlgorithm():decryptProperty.algorithm());
                    Object key = getKey(annotation.defaultKey(),decryptProperty.constantsKey(),annotation.defaultKeyProvider(),decryptProperty.keyProvider(),nativeWebRequest,sourceParameter);
                    return transcodeHandler.decrypt(key, (String) currentValue);
                }
            }
            if (annotation.decryptAll()) {
                TranscodeHandler transcodeHandler = getHandler(annotation.defaultAlgorithm());
                Object key = getKey(annotation.defaultKey(),null,annotation.defaultKeyProvider(),null,nativeWebRequest,sourceParameter);
                return transcodeHandler.decrypt(key, (String) currentValue);
            }
        }
        if (Map.class.isAssignableFrom(valueClazz)) {
            Map<Object, Object> map = (Map<Object, Object>) currentValue;
            for (Object k : map.keySet()) {
                map.put(k, deepDecrypt(currentParameterName+"."+k, map.get(k), sourceParameter, annotation, nativeWebRequest));
            }
            return currentValue;
        }
        if(List.class.isAssignableFrom(valueClazz)){
            List list = (List) currentValue;
            for (int i = 0; i < list.size(); i++) {
                list.set(i,deepDecrypt(currentParameterName+"["+i+"]",list.get(i),sourceParameter,annotation,nativeWebRequest));
            }
            return list;
        }
        Field[] fields = currentValue.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                    field.setAccessible(true);
                    field.set(currentValue, deepDecrypt(currentParameterName+"."+field.getName(), field.get(currentValue), sourceParameter, annotation, nativeWebRequest));
            } catch (IllegalAccessException e) {
                throw new DecryptException("access field error when decrypt", e);
            }
        }
        return currentValue;
    }


    private TranscodeHandler getHandler(String algorithm) {
        TranscodeHandler transcodeHandler = handlerCaches.get(algorithm);
        if (transcodeHandler != null) {
            return transcodeHandler;
        }
        for (TranscodeHandler handler : handlers) {
            if (handler.algorithm().equals(algorithm)) {
                handlerCaches.put(algorithm, handler);
                return handler;
            }
        }
        throw new DecryptException("no handler found for algorithm:" + algorithm);
    }


    private Object getKey(String defaultKey,String constantsKey,Class<? extends KeyProvider> defaultKeyProvider,Class<? extends KeyProvider> propertyKeyProvider,
                          NativeWebRequest request,Object parameterObject) {
        if(StringUtils.isNotBlank(constantsKey)){
            return constantsKey;
        }
        if(!propertyKeyProvider.equals(KeyProvider.None.class)){
            KeyProvider keyProvider = ApplicationUtil.getBean(propertyKeyProvider);
            return keyProvider.key(request,parameterObject);
        }
        if(StringUtils.isNotBlank(defaultKey)){
            return defaultKey;
        }
        if(!defaultKeyProvider.equals(KeyProvider.None.class)){
            KeyProvider keyProvider = ApplicationUtil.getBean(defaultKeyProvider);
            return keyProvider.key(request,parameterObject);
        }
       throw new DecryptException("no decrypt key provider");
    }



}
